// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/gl/gl_surface_wgl.h"

#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "base/trace_event/trace_event.h"
#include "ui/gl/gl_bindings.h"
#include "ui/gl/gl_gl_api_implementation.h"
#include "ui/gl/gl_wgl_api_implementation.h"

namespace gfx {

namespace {
    const PIXELFORMATDESCRIPTOR kPixelFormatDescriptor = {
        sizeof(kPixelFormatDescriptor), // Size of structure.
        1, // Default version.
        PFD_DRAW_TO_WINDOW | // Window drawing support.
            PFD_SUPPORT_OPENGL | // OpenGL support.
            PFD_DOUBLEBUFFER, // Double buffering support (not stereo).
        PFD_TYPE_RGBA, // RGBA color mode (not indexed).
        24, // 24 bit color mode.
        0, 0, 0, 0, 0, 0, // Don't set RGB bits & shifts.
        8, 0, // 8 bit alpha
        0, // No accumulation buffer.
        0, 0, 0, 0, // Ignore accumulation bits.
        0, // no z-buffer.
        0, // no stencil buffer.
        0, // No aux buffer.
        PFD_MAIN_PLANE, // Main drawing plane (not overlay).
        0, // Reserved.
        0, 0, 0, // Layer masks ignored.
    };

    LRESULT CALLBACK IntermediateWindowProc(HWND window,
        UINT message,
        WPARAM w_param,
        LPARAM l_param)
    {
        switch (message) {
        case WM_ERASEBKGND:
            // Prevent windows from erasing the background.
            return 1;
        case WM_PAINT:
            // Do not paint anything.
            PAINTSTRUCT paint;
            if (BeginPaint(window, &paint))
                EndPaint(window, &paint);
            return 0;
        default:
            return DefWindowProc(window, message, w_param, l_param);
        }
    }

    class DisplayWGL {
    public:
        DisplayWGL()
            : module_handle_(0)
            , window_class_(0)
            , window_handle_(0)
            , device_context_(0)
            , pixel_format_(0)
        {
        }

        ~DisplayWGL()
        {
            if (window_handle_)
                DestroyWindow(window_handle_);
            if (window_class_)
                UnregisterClass(reinterpret_cast<wchar_t*>(window_class_),
                    module_handle_);
        }

        bool Init()
        {
            // We must initialize a GL context before we can bind to extension entry
            // points. This requires the device context for a window.
            if (!GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT | GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS,
                    reinterpret_cast<wchar_t*>(IntermediateWindowProc),
                    &module_handle_)) {
                LOG(ERROR) << "GetModuleHandleEx failed.";
                return false;
            }

            WNDCLASS intermediate_class;
            intermediate_class.style = CS_OWNDC;
            intermediate_class.lpfnWndProc = IntermediateWindowProc;
            intermediate_class.cbClsExtra = 0;
            intermediate_class.cbWndExtra = 0;
            intermediate_class.hInstance = module_handle_;
            intermediate_class.hIcon = LoadIcon(NULL, IDI_APPLICATION);
            intermediate_class.hCursor = LoadCursor(NULL, IDC_ARROW);
            intermediate_class.hbrBackground = NULL;
            intermediate_class.lpszMenuName = NULL;
            intermediate_class.lpszClassName = L"Intermediate GL Window";
            window_class_ = RegisterClass(&intermediate_class);
            if (!window_class_) {
                LOG(ERROR) << "RegisterClass failed.";
                return false;
            }

            window_handle_ = CreateWindowEx(WS_EX_NOPARENTNOTIFY,
                reinterpret_cast<wchar_t*>(window_class_),
                L"",
                WS_OVERLAPPEDWINDOW,
                0,
                0,
                100,
                100,
                NULL,
                NULL,
                NULL,
                NULL);
            if (!window_handle_) {
                LOG(ERROR) << "CreateWindow failed.";
                return false;
            }

            device_context_ = GetDC(window_handle_);
            pixel_format_ = ChoosePixelFormat(device_context_,
                &kPixelFormatDescriptor);
            if (pixel_format_ == 0) {
                LOG(ERROR) << "Unable to get the pixel format for GL context.";
                return false;
            }
            if (!SetPixelFormat(device_context_,
                    pixel_format_,
                    &kPixelFormatDescriptor)) {
                LOG(ERROR) << "Unable to set the pixel format for temporary GL context.";
                return false;
            }

            return true;
        }

        ATOM window_class() const { return window_class_; }
        HDC device_context() const { return device_context_; }
        int pixel_format() const { return pixel_format_; }

    private:
        HINSTANCE module_handle_;
        ATOM window_class_;
        HWND window_handle_;
        HDC device_context_;
        int pixel_format_;
    };
    DisplayWGL* g_display;
} // namespace

GLSurfaceWGL::GLSurfaceWGL()
{
}

GLSurfaceWGL::~GLSurfaceWGL()
{
}

void* GLSurfaceWGL::GetDisplay()
{
    return GetDisplayDC();
}

bool GLSurfaceWGL::InitializeOneOff()
{
    static bool initialized = false;
    if (initialized)
        return true;

    DCHECK(g_display == NULL);
    scoped_ptr<DisplayWGL> wgl_display(new DisplayWGL);
    if (!wgl_display->Init())
        return false;

    g_display = wgl_display.release();
    initialized = true;
    return true;
}

void GLSurfaceWGL::InitializeOneOffForTesting()
{
    if (g_display == NULL) {
        g_display = new DisplayWGL;
    }
}

HDC GLSurfaceWGL::GetDisplayDC()
{
    return g_display->device_context();
}

NativeViewGLSurfaceWGL::NativeViewGLSurfaceWGL(gfx::AcceleratedWidget window)
    : window_(window)
    , child_window_(NULL)
    , device_context_(NULL)
{
    DCHECK(window);
}

NativeViewGLSurfaceWGL::~NativeViewGLSurfaceWGL()
{
    Destroy();
}

bool NativeViewGLSurfaceWGL::Initialize(GLSurface::Format format)
{
    DCHECK(!device_context_);

    RECT rect;
    if (!GetClientRect(window_, &rect)) {
        LOG(ERROR) << "GetClientRect failed.\n";
        Destroy();
        return false;
    }

    // Create a child window. WGL has problems using a window handle owned by
    // another process.
    child_window_ = CreateWindowEx(WS_EX_NOPARENTNOTIFY,
        reinterpret_cast<wchar_t*>(g_display->window_class()),
        L"",
        WS_CHILDWINDOW | WS_DISABLED | WS_VISIBLE,
        0,
        0,
        rect.right - rect.left,
        rect.bottom - rect.top,
        window_,
        NULL,
        NULL,
        NULL);
    if (!child_window_) {
        LOG(ERROR) << "CreateWindow failed.\n";
        Destroy();
        return false;
    }

    // The GL context will render to this window.
    device_context_ = GetDC(child_window_);
    if (!device_context_) {
        LOG(ERROR) << "Unable to get device context for window.";
        Destroy();
        return false;
    }

    if (!SetPixelFormat(device_context_,
            g_display->pixel_format(),
            &kPixelFormatDescriptor)) {
        LOG(ERROR) << "Unable to set the pixel format for GL context.";
        Destroy();
        return false;
    }

    return true;
}

void NativeViewGLSurfaceWGL::Destroy()
{
    if (child_window_ && device_context_)
        ReleaseDC(child_window_, device_context_);

    if (child_window_)
        DestroyWindow(child_window_);

    child_window_ = NULL;
    device_context_ = NULL;
}

bool NativeViewGLSurfaceWGL::IsOffscreen()
{
    return false;
}

gfx::SwapResult NativeViewGLSurfaceWGL::SwapBuffers()
{
    TRACE_EVENT2("gpu", "NativeViewGLSurfaceWGL:RealSwapBuffers",
        "width", GetSize().width(),
        "height", GetSize().height());

    // Resize the child window to match the parent before swapping. Do not repaint
    // it as it moves.
    RECT rect;
    if (!GetClientRect(window_, &rect))
        return gfx::SwapResult::SWAP_FAILED;
    if (!MoveWindow(child_window_,
            0,
            0,
            rect.right - rect.left,
            rect.bottom - rect.top,
            FALSE)) {
        return gfx::SwapResult::SWAP_FAILED;
    }

    DCHECK(device_context_);
    return ::SwapBuffers(device_context_) == TRUE ? gfx::SwapResult::SWAP_ACK
                                                  : gfx::SwapResult::SWAP_FAILED;
}

gfx::Size NativeViewGLSurfaceWGL::GetSize()
{
    RECT rect;
    BOOL result = GetClientRect(child_window_, &rect);
    DCHECK(result);
    return gfx::Size(rect.right - rect.left, rect.bottom - rect.top);
}

void* NativeViewGLSurfaceWGL::GetHandle()
{
    return device_context_;
}

PbufferGLSurfaceWGL::PbufferGLSurfaceWGL(const gfx::Size& size)
    : size_(size)
    , device_context_(NULL)
    , pbuffer_(NULL)
{
    // Some implementations of Pbuffer do not support having a 0 size. For such
    // cases use a (1, 1) surface.
    if (size_.GetArea() == 0)
        size_.SetSize(1, 1);
}

PbufferGLSurfaceWGL::~PbufferGLSurfaceWGL()
{
    Destroy();
}

bool PbufferGLSurfaceWGL::Initialize(GLSurface::Format format)
{
    DCHECK(!device_context_);

    if (!gfx::g_driver_wgl.fn.wglCreatePbufferARBFn) {
        LOG(ERROR) << "wglCreatePbufferARB not available.";
        Destroy();
        return false;
    }

    const int kNoAttributes[] = { 0 };
    pbuffer_ = wglCreatePbufferARB(g_display->device_context(),
        g_display->pixel_format(),
        size_.width(), size_.height(),
        kNoAttributes);

    if (!pbuffer_) {
        LOG(ERROR) << "Unable to create pbuffer.";
        Destroy();
        return false;
    }

    device_context_ = wglGetPbufferDCARB(static_cast<HPBUFFERARB>(pbuffer_));
    if (!device_context_) {
        LOG(ERROR) << "Unable to get pbuffer device context.";
        Destroy();
        return false;
    }

    return true;
}

void PbufferGLSurfaceWGL::Destroy()
{
    if (pbuffer_ && device_context_)
        wglReleasePbufferDCARB(static_cast<HPBUFFERARB>(pbuffer_), device_context_);

    device_context_ = NULL;

    if (pbuffer_) {
        wglDestroyPbufferARB(static_cast<HPBUFFERARB>(pbuffer_));
        pbuffer_ = NULL;
    }
}

bool PbufferGLSurfaceWGL::IsOffscreen()
{
    return true;
}

gfx::SwapResult PbufferGLSurfaceWGL::SwapBuffers()
{
    NOTREACHED() << "Attempted to call SwapBuffers on a pbuffer.";
    return gfx::SwapResult::SWAP_FAILED;
}

gfx::Size PbufferGLSurfaceWGL::GetSize()
{
    return size_;
}

void* PbufferGLSurfaceWGL::GetHandle()
{
    return device_context_;
}

} // namespace gfx
